Ext.form.BasicForm = Ext.extend(Ext.util.Observable,
{
	constructor: function( el, config )
	{
		Ext.apply(this, config);
		if( Ext.isString(this.paramOrder) )
		{
			this.paramOrder = this.paramOrder.split(/[\s,|]/);
		}

		this.items = new Ext.util.MixedCollection(false, function( o )
		{
			return o.getItemId();
		});
		this.addEvents('beforeaction', 'actionfailed', 'actioncomplete');

		if( el )
		{
			this.initEl(el);
		}
		Ext.form.BasicForm.superclass.constructor.call(this);
	},

	timeout: 30,

	paramOrder: undefined,
	paramsAsHash: false,

	waitTitle: 'Please Wait...',

	// private
	activeAction: null,
	trackResetOnLoad: false,

	// private
	initEl: function( el )
	{
		this.el = Ext.get(el);
		this.id = this.el.id || Ext.id();
		
		//如果是标准提交，那么e.stopEvent()
		if( !this.standardSubmit )
		{
			this.el.on('submit', this.onSubmit, this);
		}
		this.el.addClass('x-form');
	},

	getEl: function()
	{
		return this.el;
	},

	// private
	onSubmit: function( e )
	{
		e.stopEvent();
	},

	destroy: function( bound )
	{
		if( bound !== true )
		{
			this.items.each(function( f )
			{
				Ext.destroy(f);
			});
			Ext.destroy(this.el);
		}
		this.items.clear();
		this.purgeListeners();
	},

	isValid: function()
	{
		var valid = true;
		this.items.each(function( f )
		{
			if( !f.validate() )
			{
				valid = false;
			}
		});
		return valid;
	},

	isDirty: function()
	{
		var dirty = false;
		this.items.each(function( f )
		{
			if( f.isDirty() )
			{
				dirty = true;
				return false;
			}
		});
		return dirty;
	},

	doAction: function( action, options )
	{
		if( Ext.isString(action) )
		{
			action = new Ext.form.Action.ACTION_TYPES[action](this, options);
		}
		if( this.fireEvent('beforeaction', this, action) !== false )
		{
			this.beforeAction(action);
			action.run.defer(100, action);
		}
		return this;
	},

	submit: function( options )
	{
		options = options || {};
		if( this.standardSubmit )
		{
			var v = options.clientValidation === false || this.isValid();
			if( v )
			{
				var el = this.el.dom;
				if( this.url && Ext.isEmpty(el.action) )
				{
					el.action = this.url;
				}
				el.submit();
			}
			return v;
		}
		var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
		this.doAction(submitAction, options);
		return this;
	},

	/**
	 * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Load load action}.
	 * 
	 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
	 * @return {BasicForm} this
	 */
	load: function( options )
	{
		var loadAction = String.format('{0}load', this.api ? 'direct' : '');
		this.doAction(loadAction, options);
		return this;
	},
	updateRecord: function( record )
	{
		record.beginEdit();
		var fs = record.fields, field, value;
		fs.each(function( f )
		{
			field = this.findField(f.name);
			if( field )
			{
				value = field.getValue();
				if( Ext.type(value) !== false && value.getGroupValue )
				{
					value = value.getGroupValue();
				}
				else if( field.eachItem )
				{
					value = [];
					field.eachItem(function( item )
					{
						value.push(item.getValue());
					});
				}
				record.set(f.name, value);
			}
		}, this);
		record.endEdit();
		return this;
	},

	/**
	 * Loads an {@link Ext.data.Record} into this form by calling {@link #setValues} with the {@link Ext.data.Record#data record data}. See also
	 * {@link #trackResetOnLoad}.
	 * 
	 * @param {Record} record The record to load
	 * @return {BasicForm} this
	 */
	loadRecord: function( record )
	{
		this.setValues(record.data);
		return this;
	},

	// private
	beforeAction: function( action )
	{
		// Call HtmlEditor's syncValue before actions
		this.items.each(function( f )
		{
			if( f.isFormField && f.syncValue )
			{
				f.syncValue();
			}
		});
		var o = action.options;
		if( o.waitMsg )
		{
			if( this.waitMsgTarget === true )
			{
				this.el.mask(o.waitMsg, 'x-mask-loading');
			}
			else if( this.waitMsgTarget )
			{
				this.waitMsgTarget = Ext.get(this.waitMsgTarget);
				this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
			}
			else
			{
				Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle);
			}
		}
	},

	// private
	afterAction: function( action, success )
	{
		this.activeAction = null;
		var o = action.options;
		if( o.waitMsg )
		{
			if( this.waitMsgTarget === true )
			{
				this.el.unmask();
			}
			else if( this.waitMsgTarget )
			{
				this.waitMsgTarget.unmask();
			}
			else
			{
				Ext.MessageBox.updateProgress(1);
				Ext.MessageBox.hide();
			}
		}
		if( success )
		{
			if( o.reset )
			{
				this.reset();
			}
			Ext.callback(o.success, o.scope,
			[
			    this, action
			]);
			this.fireEvent('actioncomplete', this, action);
		}
		else
		{
			Ext.callback(o.failure, o.scope,
			[
			    this, action
			]);
			this.fireEvent('actionfailed', this, action);
		}
	},

	/**
	 * Find a {@link Ext.form.Field} in this form.
	 * 
	 * @param {String} id The value to search for (specify either a {@link Ext.Component#id id}, {@link Ext.grid.Column#dataIndex dataIndex},
	 *        {@link Ext.form.Field#getName name or hiddenName}).
	 * @return Field
	 */
	findField: function( id )
	{
		var field = this.items.get(id);

		if( !Ext.isObject(field) )
		{
			// searches for the field corresponding to the given id. Used recursively for composite fields
			var findMatchingField = function( f )
			{
				if( f.isFormField )
				{
					if( f.dataIndex == id || f.id == id || f.getName() == id )
					{
						field = f;
						return false;
					}
					else if( f.isComposite )
					{
						return f.items.each(findMatchingField);
					}
					else if( f instanceof Ext.form.CheckboxGroup && f.rendered )
					{
						return f.eachItem(findMatchingField);
					}
				}
			};

			this.items.each(findMatchingField);
		}
		return field || null;
	},

	/**
	 * Mark fields in this form invalid in bulk.
	 * 
	 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
	 * @return {BasicForm} this
	 */
	markInvalid: function( errors )
	{
		if( Ext.isArray(errors) )
		{
			for( var i = 0, len = errors.length; i < len; i++ )
			{
				var fieldError = errors[i];
				var f = this.findField(fieldError.id);
				if( f )
				{
					f.markInvalid(fieldError.msg);
				}
			}
		}
		else
		{
			var field, id;
			for( id in errors )
			{
				if( !Ext.isFunction(errors[id]) && (field = this.findField(id)) )
				{
					field.markInvalid(errors[id]);
				}
			}
		}

		return this;
	},

	setValues: function( values )
	{
		if( Ext.isArray(values) )
		{ // array of objects
			for( var i = 0, len = values.length; i < len; i++ )
			{
				var v = values[i];
				var f = this.findField(v.id);
				if( f )
				{
					f.setValue(v.value);
					if( this.trackResetOnLoad )
					{
						f.originalValue = f.getValue();
					}
				}
			}
		}
		else
		{ // object hash
			var field, id;
			for( id in values )
			{
				if( !Ext.isFunction(values[id]) && (field = this.findField(id)) )
				{
					field.setValue(values[id]);
					if( this.trackResetOnLoad )
					{
						field.originalValue = field.getValue();
					}
				}
			}
		}
		return this;
	},

	getValues: function( asString )
	{
		var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
		if( asString === true )
		{
			return fs;
		}
		return Ext.urlDecode(fs);
	},

	getFieldValues: function( dirtyOnly )
	{
		var o = {}, n, key, val;
		this.items.each(function( f )
		{
			if( !f.disabled && (dirtyOnly !== true || f.isDirty()) )
			{
				n = f.getName();
				key = o[n];
				val = f.getValue();

				if( Ext.isDefined(key) )
				{
					if( Ext.isArray(key) )
					{
						o[n].push(val);
					}
					else
					{
						o[n] =
						[
						    key, val
						];
					}
				}
				else
				{
					o[n] = val;
				}
			}
		});
		return o;
	},

	clearInvalid: function()
	{
		this.items.each(function( f )
		{
			f.clearInvalid();
		});
		return this;
	},

	/**
	 * Resets this form.
	 * 
	 * @return {BasicForm} this
	 */
	reset: function()
	{
		this.items.each(function( f )
		{
			f.reset();
		});
		return this;
	},

	add: function()
	{
		this.items.addAll(Array.prototype.slice.call(arguments, 0));
		return this;
	},

	/**
	 * Removes a field from the items collection (does NOT remove its markup).
	 * 
	 * @param {Field} field
	 * @return {BasicForm} this
	 */
	remove: function( field )
	{
		this.items.remove(field);
		return this;
	},

	/**
	 * Removes all fields from the collection that have been destroyed.
	 */
	cleanDestroyed: function()
	{
		this.items.filterBy(function( o )
		{
			return !!o.isDestroyed;
		}).each(this.remove, this);
	},

	/**
	 * Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm, checks them for an id attribute, and calls
	 * {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
	 * 
	 * @return {BasicForm} this
	 */
	render: function()
	{
		this.items.each(function( f )
		{
			if( f.isFormField && !f.rendered && document.getElementById(f.id) )
			{ // if the element exists
				f.applyToMarkup(f.id);
			}
		});
		return this;
	},

	/**
	 * Calls {@link Ext#apply} for all fields in this form with the passed object.
	 * 
	 * @param {Object} values
	 * @return {BasicForm} this
	 */
	applyToFields: function( o )
	{
		this.items.each(function( f )
		{
			Ext.apply(f, o);
		});
		return this;
	},

	/**
	 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
	 * 
	 * @param {Object} values
	 * @return {BasicForm} this
	 */
	applyIfToFields: function( o )
	{
		this.items.each(function( f )
		{
			Ext.applyIf(f, o);
		});
		return this;
	},

	callFieldMethod: function( fnName, args )
	{
		args = args || [];
		this.items.each(function( f )
		{
			if( Ext.isFunction(f[fnName]) )
			{
				f[fnName].apply(f, args);
			}
		});
		return this;
	}
});

// back compat
Ext.BasicForm = Ext.form.BasicForm;
